unit BDPgmCalMTMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls,
  Vcl.Imaging.pngimage, System.Actions, Vcl.ActnList, Vcl.Touch.GestureMgr,
  CPortCtl, CPort, Vcl.ComCtrls, BD_LabJack, Vcl.Grids, BD_UUT, BD_TestHead,
  Vcl.Menus, ksTimers, IDGlobal, System.SyncObjs, iniFiles, BD_PgmCalMT_Aux;

(****************************************************************************************
  TTester class contains the objects that make up a test system- the LJ (labjack) object,
  the UUT (Unit Under Test) object, and the TH (Test Head) object. This model allows
  multiple Testers to be connected to a single PC, with distinct I/O.
*****************************************************************************************)
type
  TTester = class
    public
      LJ: TLabJack;
      UUT: TUUT;
      TH: TTestHead;
      constructor Create;
  end;

type
  TfrmMain = class(TForm)
    sbStatus: TStatusBar;
    Label1: TLabel;
    sgADData: TStringGrid;
    rgMode: TRadioGroup;
    sgSN: TStringGrid;
    Label4: TLabel;
    Label3: TLabel;
    tmrADRead: TTimer;
    btnClearData: TButton;
    pnlCal: TPanel;
    btnCaptureZero: TButton;
    btnCaptureMax: TButton;
    btnSendParams: TButton;
    pnlTest: TPanel;
    btnLedOn: TButton;
    btnSegsOn: TButton;
    btnSegsOff: TButton;
    btnLedOff: TButton;
    btnReadAD: TButton;
    btnReadADCont: TButton;
    btnExitCal: TButton;
    btnReadSN: TButton;
    btnSetSN: TButton;
    btnReset: TButton;
    Label5: TLabel;
    Label6: TLabel;
    lblADReading: TLabel;
    pnlPressure: TPanel;
    cbFastest: TCheckBox;
    cbSeal: TCheckBox;
    cbVent: TCheckBox;
    cbHighPress: TCheckBox;
    ComFP5: TComPort;
    FP5Term: TComTerminal;
    Label7: TLabel;
    Label8: TLabel;
    Button1: TButton;
    btnLeakTest: TButton;
    Panel1: TPanel;
    lblPress: TLabel;
    Label9: TLabel;
    lblLeakRate: TLabel;
    Label10: TLabel;
    Label11: TLabel;
    btnGetPress: TButton;
    pbLeakTime: TProgressBar;
    Label2: TLabel;
    lblDecayVal: TLabel;
    pnlBattery: TPanel;
    Label12: TLabel;
    lblBattV: TLabel;
    Label13: TLabel;
    btnGetBattV: TButton;
    MainMenu1: TMainMenu;
    Settings1: TMenuItem;
    FP5COMSettings1: TMenuItem;
    Button2: TButton;
    Memo1: TMemo;
    cbTestHead: TComboBox;
    sgUUTParameters: TStringGrid;
    Label14: TLabel;
    pnlUUTInfo: TPanel;
    Label15: TLabel;
    Label16: TLabel;
    edWorkOrder: TEdit;
    edSN: TEdit;
    udIDNum: TUpDown;
    btnProgCal: TButton;
    pnlProgCal: TPanel;
    btnProgram: TButton;
    btnAutoCal: TButton;
    btnCal: TButton;
    tmrProg: TTimer;
    dpProgOK: TComDataPacket;
    btnResetSys: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    //procedure ComPort1RxChar(Sender: TObject; Count: Integer);
    procedure StrToBytes(AStr: string; var rVal: TByteArr);
    procedure CalEnterCal;
    procedure ScrollToBottom;
    function ScrollRichEditToBottom(var RichEdit: TRichEdit): Boolean;
    procedure btnReadADClick(Sender: TObject);
    procedure rgModeClick(Sender: TObject);
    procedure ResetUUT;
    procedure pgmUUT;
    procedure CalSendParams;
    procedure CalSetSN;
    procedure CalReadSN;
    procedure CalExitCal;
    procedure btnReadADContClick(Sender: TObject);
    procedure tmrADReadTimer(Sender: TObject);
    procedure btnClearDataClick(Sender: TObject);
    procedure CalCaptureZero;
    procedure CalCaptureMax;
    procedure CalCaptureRtz;
    procedure btnSegsOffClick(Sender: TObject);
    procedure btnSegsOnClick(Sender: TObject);
    procedure btnLedOnClick(Sender: TObject);
    procedure btnLedOffClick(Sender: TObject);
    procedure btnGetPressClick(Sender: TObject);
    procedure cbFastestClick(Sender: TObject);
    procedure cbVentClick(Sender: TObject);
    procedure cbSealClick(Sender: TObject);
    procedure cbHighPressClick(Sender: TObject);
    procedure btnProgramClick(Sender: TObject);
    procedure CalAutoCal;
    procedure GetUUTPressure;     //Reads the pressure applied to the UUT
    procedure ProcessCalBtns(Sender: TObject);
    procedure CalLeakTest;
    procedure btnGetBattVClick(Sender: TObject);
    procedure FP5COMSettings1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);    //LCD/LED display utilities
    procedure adCaptureVals;
    procedure LoadIniSettings;
    procedure ConfigCalAccess;
    procedure btnProgCalClick(Sender: TObject);
    procedure dpProgOKPacket(Sender: TObject; const Str: string);
    procedure tmrProgTimer(Sender: TObject);
    procedure btnResetSysClick(Sender: TObject);
    procedure edSNChange(Sender: TObject);
    procedure edSNEnter(Sender: TObject);


  private
    { Private declarations }
    AppSettings: TIniFile;

   
  public
    { Public declarations }
    AutoCal: Boolean;
    CalAfterPgm: boolean;    //tells sytem to auto start cal after programming.
                             //also used to indicate that a log record should be made.
    ThADCnt: Integer;   //counter to be used by outside thread for A/D averaging
    //Test head objects
    TH1: TTester;
    TH2: TTester;
    SysLabJacks: TSysLJArray;  //Record for each LJ found attached to the system
  end;

var
  frmMain: TfrmMain;  LJ: TLabJack;  UUT: TUUT; TH: TTestHead; EvDone: TEvent;
  cfgParams: TCfgFileParams;

implementation

{$R *.dfm}


(*******************************************************************************
 btnLedOff: Turn LCD backlight off
*******************************************************************************)
procedure TfrmMain.btnLedOffClick(Sender: TObject);
begin
  UUT.SendCmd2(csLedOff);
end;

(*******************************************************************************
 btnLedOn: Turn LCD backlight on
*******************************************************************************)
procedure TfrmMain.btnLedOnClick(Sender: TObject);
begin
 UUT.SendCmd2(csLedOn);
end;

(*******************************************************************************
 btnProgCal: Causes the FP5 Programmer to erase, program, and verify the target
          device, then begins autocal.
*******************************************************************************)
procedure TfrmMain.btnProgCalClick(Sender: TObject);
begin
  if(edSN.Text <> '') and (edWorkOrder.Text <> '') then
  begin
    if Assigned(UUT) then
    UUT.Free;
    UUT := TUUT.Create;
    UUT.InitParams;
    CalAfterPgm := True;   //auto start calibration after programming;
    pgmUUT;
  end
  else
    MemMsg('Work Order and Syringe S/N fields must be populated' + #13 +
            'before test can be performed.');
end;

(*******************************************************************************
 btnProgram: call procedure to erase, program, and verify the target
          device.
*******************************************************************************)
procedure TfrmMain.btnProgramClick(Sender: TObject);
begin
  CalAfterPgm := False;   //do not autocal after programming
  pgmUUT;
end;

(*******************************************************************************
 pgmUUT: Causes the FP5 Programmer to erase, program, and verify the target
          device.
*******************************************************************************)
procedure TfrmMain.pgmUUT;
begin
  pnlCal.Enabled := False;
  pnlTest.Enabled := False;
  pnlProgCal.Enabled := False;
  MemMsg('Programming UUT.');
  UUT.UUTReady := False;        //Initialize in case was set true by prior operations.
  UUT.SendCmd2(csBattV,bvPreProg); //records and displays battery voltage prior to programming
  if rgMode.ItemIndex <> 0 then    //ensure in programming mode
    begin
      rgMode.ItemIndex := 0;
      Sleep(200);         //allow relays time to change over
    end;
  ComFP5.WriteStr('ep'+#10+#13);    //complete programming
  tmrProg.Enabled := True;
  //Add code to start a time (to allow a max time to try to program) and a
  //ComPort RX Packet event that will respond to a program success or a
  //program failure message from the programmer.
end;
(*******************************************************************************
 btnSegsOff: Turn all LCD segments off
*******************************************************************************)
procedure TfrmMain.btnSegsOffClick(Sender: TObject);
begin
UUT.SendCmd2(csLcdOff);
end;
(*******************************************************************************
 btnSegsOn: Turn on all LCD segments.
*******************************************************************************)
procedure TfrmMain.btnSegsOnClick(Sender: TObject);
begin
UUT.SendCmd2(csLcdOn);
end;

//Test proceedure- remove in final version
procedure TfrmMain.Button2Click(Sender: TObject);
var
  i: Integer;
begin
  FindAllLabJacks(SysLabJacks);
  for i := Low(SysLabJacks) to High(SysLabJacks) do
    Memo1.Lines.Add('***LJ '+ i.ToString + '***' +
                    '   S/N: '+SysLabJacks[i].Sn.ToString +
                    '   ID: '+ SysLabJacks[i].Id.ToString +
                    '   ADDR: '+ FloatToStr(SysLabJacks[i].Sn));

end;

(*******************************************************************************
btnClearData: Clears all data from the memo
*******************************************************************************)
procedure TfrmMain.btnClearDataClick(Sender: TObject);
begin
  Memo1.Lines.Clear;
end;

(*******************************************************************************
 btnReadADClick: Configures the RX state machine to look for  A to D data from
                 the UUT.
*******************************************************************************)
procedure TfrmMain.btnReadADClick(Sender: TObject);
var
  i: Integer;
begin
  UUT.NumReadsRequired := 1;
  UUT.WaitForAD := True;
  for i := 0 to 27 do
    UUT.ADArrayAvg[i] := 0;   //clear the buffer.
  UUT.ADCapture := adOnce;
  UUT.SendCmd2(csCaptureOnce, UUT.NumReadsRequired);
end;
(*******************************************************************************
 btnReadADClickCont: Enables timer to continuously configures the RX state
                     machine to look for  A to D data from the UUT.
*******************************************************************************)
procedure TfrmMain.btnReadADContClick(Sender: TObject);
const
  {$J+}
  BtnDown: Boolean = False;
  {$J-}
begin
  if BtnDown then
    begin
      BtnDown := False;
      tmrADRead.Enabled := False;
      lblADReading.Visible := False;
      sgADData.Cells[1,0] := 'A/D Zero';
    end
    else
      begin
        sgADData.Cells[1,0] := 'A/D Value';
        BtnDown := True;
        tmrADRead.Enabled := True;
      end;
end;

(*******************************************************************************
 FullResetTester: Perform a reset of the test head hardware, and the GUI
*******************************************************************************)
procedure TfrmMain.btnResetSysClick(Sender: TObject);
begin
  FullResetTester;
end;

(*******************************************************************************
 btnFastestClick: Toggle Fastest Head
*******************************************************************************)
procedure TfrmMain.cbFastestClick(Sender: TObject);
begin
  cbFastest.Checked := TH.SetFastest(cbFastest.Checked);
end;
(*******************************************************************************
 btnHighPressClick: Toggle High/Low pressure solenoid
*******************************************************************************)
procedure TfrmMain.cbHighPressClick(Sender: TObject);
begin
  cbHighPress.Checked := TH.SetHighLow(cbHighPress.Checked);
end;
                                            
(*******************************************************************************
 btnFastestClick: Toggle Seal solenoid
*******************************************************************************)
procedure TfrmMain.cbSealClick(Sender: TObject);
begin
  cbSeal.Checked := TH.SetSeal(cbSeal.Checked);
end;

(*******************************************************************************
 btnVentClick: Toggle Vent solenoid
*******************************************************************************)
procedure TfrmMain.cbVentClick(Sender: TObject);
begin
  cbVent.Checked := TH.SetVent(cbVent.Checked);
end;
 
(*******************************************************************************
 btnGetPressClick: Read the pressure from the Sensotec and display on GUI
*******************************************************************************)
procedure TfrmMain.btnGetPressClick(Sender: TObject);
begin
  GetUUTPressure;
end;

(*******************************************************************************
 btnGetBattVClick: Read the Battery voltage and display on GUI
*******************************************************************************)
procedure TfrmMain.btnGetBattVClick(Sender: TObject);
begin
  if not AutoCal then
    UUT.SendCmd2(csBattV, bvNone);
end;

(*******************************************************************************
 GetUUTPressure: Read the pressure from the Sensotec and Display.
 ******************************************************************************)
procedure TfrmMain.GetUUTPressure;
var
  tmp: Double; i: Integer;
begin
  UUT.PresCurrent := 0;
  for i := 1 to 10 do
    begin
      LJ.GetAnalogInput(1,tmp);
      UUT.PresCurrent := UUT.PresCurrent + tmp;
      Sleep(1);
    end;
  UUT.PresCurrent := UUT.PresCurrent/10;      //get average reading
  UUT.PresCurrent := UUT.PresCurrent*100;
  lblPress.Caption := FloatToStrF(UUT.PresCurrent, ffFixed, 15,1) +
                      ' PSI / ' + FloatToStrF(UUT.PresCurrent/14.69595, ffFixed, 15, 1) +
                      ' ATM';
end;

(*******************************************************************************
 FormClose: Performs clean up actions when form is closed (and application is
             exited).
*******************************************************************************)
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
var
  i: Integer;
begin
  //Turn solenoids off
  cbHighPress.Checked := False;
  cbVent.Checked := False;
  cbSeal.Checked := False;
  cbFastest.Checked := False;
  //Save settings to inifile -temporary
  if Assigned(AppSettings) then
    begin
      try
        for i := 0 to Length(SysLabJacks)-1 do
          AppSettings.WriteInteger('LJCONFIG', 'LJ_'+i.ToString+'_SN',SysLabJacks[i].Sn);
      except
        ShowMessage('Error while saving application setting .ini file.');
      end;
      AppSettings.Free;
    end;
  //Free objects and release mem
  LJ.Free;
  TH.Free;
  UUT.Free;
  LJ.CloseLabJack;
end;

(*******************************************************************************
 FormCreate: Performs initialization tasks when application is first started
             at time that form is created.
*******************************************************************************)
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  //First thing to do is check for presence of LabJacks, compare against the value in the
  //.ini file, and if there is a difference, run the setup option.
  FindAllLabJacks(SysLabJacks);
  LoadIniSettings;
  UUT := TUUT.Create;
  UUT.InitParams;
  LJ := TLabJack.Create;
  TH := TTestHead.Create;
  InitParamGrid;
  try
     LJ.InitLabJack;
  except
    ShowMessage('Error occured while connecting LabJack or opening ComPort.');
  end;
  if FileExists(ExtractFilePath(Application.ExeName)+'FP5ComSettings.ini') then
    try
      ComFP5.LoadSettings(stIniFile, ExtractFilePath(Application.ExeName)+'FP5ComSettings.ini' );
      ComFP5.Open;

    except
      ShowMessage('Unable to load FP5ComSettings.ini. Unable to open port.');
    end;
  //+++++++Need to load these next two arrays with data from files- not hard coded
  FillADDataConsts(sgADData);
  FillSNConsts(sgSN);
  //Initialize UUT parameters
  TH.Intialize;
  LoadCfgParams(cfgParams);   //initiallize the parameters from the cfg file.
  rgMode.ItemIndex := 0;
  rgModeClick(Self);
  Sleep(50);
  rgMode.ItemIndex := 1;
  rgModeClick(Self);
end;

(*******************************************************************************
 FO5COMSettings: Set the COM Port settings for the FP5 Pgmr. User will need
                 to know which port is connected to the Pgmr, using control panel.
******************************************************************************)
procedure TfrmMain.FP5COMSettings1Click(Sender: TObject);
var
  fp: string;
begin
  ShowMessage('FP5 Programmer should be configured for 9600, 8-N-1, '+#13 +
              'Flow Control = Hardware' +
              'on the appropriate COM port.');
  fp := ExtractFilePath(Application.ExeName);
  ComFP5.ShowSetupDialog;
  ComFP5.StoreSettings(stIniFile, fp+'FP5ComSettings.ini');
  if not ComFP5.Connected then
    try
      ComFP5.Open;
    except
      ShowMessage('COM PORT' + ComFP5.Port +' could not be opened.');
    end;
end;

(*******************************************************************************
 rgModeClick: Configure relays to connect to serial port (instead of NEC
              programmer) to allow serial port comms with UUT.
*******************************************************************************)
procedure TfrmMain.rgModeClick(Sender: TObject);
begin
  if rgMode.ItemIndex = 1 then
    begin
      //++++++Need to update with constants
      LJ.PutBit(4,0);
      sleep(50);
      LJ.PutBit(5,0);
      sleep(50);
      LJ.PutBit(7,0);
      pnlProgCal.Enabled := True;
      btnAutoCal.Enabled := True;
      btnCal.Enabled := True;
      btnProgram.Enabled := False;
    end
    else
      begin
        LJ.PutBit(4,1);
        sleep(50);
        LJ.PutBit(5,1);
        sleep(50);
        LJ.PutBit(7,1);
        pnlProgCal.Enabled := True;
        btnAutoCal.Enabled := False;
        btnCal.Enabled := False;
        btnProgram.Enabled := True;
    end
end;


(*******************************************************************************
 StrToBytes: Converts a text string of hex digits into an array of bytes.
*******************************************************************************)
procedure TfrmMain.StrToBytes(AStr: string; var rVal: TByteArr);
var
  bVal: TByteArr;
  hNib: byte;
  j, i: Integer;
  IsOdd: Boolean;
begin
  try
      IsOdd := Odd(Length(AStr));
       if IsOdd then
        AStr := '0'+AStr;               //make the tmp an even length (add leading 0 if necessary) so cann for full byte
      SetLength(bVal, Length(AStr));
      SetLength(rVal, Length(bVal) div 2);    //rVal holds the packed byte- half the length of AStr
      for j := 1 to Length(AStr)do
        begin
          if (ord(AStr[j])) <= $39 then        //'9' is 57 in ASCII
           bVal[j-1] := byte(AStr[j]) - $30      //subtract 48 from ASCII numeral equivalents
           else
            bVal[j-1] := byte(AStr[j]) - $37;    //subtract 55 from A-F chars (hex numbers)
         end;
      i := 0;
      j := 0;
      while j <= Length(bVal)-1 do
        begin
          hNib := (bVal[j] shl 4);         //shift hNib to upper 4 bits.
          inc(j);
          rVal[i] := (hNib or bVal[j]);
          inc(j);
          inc(i);
        end;
  except
    ShowMessage('Invalid number.');
  end;
end;
(*******************************************************************************
 tmrADRead: Configures thread to  continousuly read  A to D
            data from the UUT.
*******************************************************************************)
procedure TfrmMain.tmrADReadTimer(Sender: TObject);
begin
  if lblADReading.Visible = False then
    lblADReading.Visible := True
    else
      lblADReading.Visible := False;
  if not UUT.WaitForAd then
    btnReadAD.Click;
end;

(*******************************************************************************
 tmrProg: Fires if the correct response from the programmer is not received
          within a specified time after the program button is pressed.
*******************************************************************************)
procedure TfrmMain.tmrProgTimer(Sender: TObject);
begin
  tmrProg.Enabled := False;
  UUT.UUTProgrammed := False;
  MemMsg('UUT Failed to program. Check connections and retry.');
end;

(*******************************************************************************
 ProcessCalBtns: Read the id of the cal panel button that was pressed, and set
                 up the RX state machine accordingly
 ******************************************************************************)
procedure TfrmMain.ProcessCalBtns(Sender: TObject);
begin
  if TButton(Sender).Tag > 0 then
    AutoCal := False;   //need to force this for all buttons except autocal.
  case TButton(Sender).Tag of
    0: CalAutoCal;
    1: ResetUUT;
    2: CalEnterCal;
    3: CalSetSN;
    4: CalReadSN;
    5: CalCaptureZero;
    6: CalCaptureMax;
    7: CalSendParams;
    8: CalExitCal;
    9: CalCaptureRtz;
    10: CalLeakTest;
  end;
end;
(*******************************************************************************
 ResetUUT: Issues reset to the UUT.
*******************************************************************************)
procedure TfrmMain.ResetUUT;
begin
  UUT.SendCmd2(csReset);
end;

(*******************************************************************************
 adCaptureVal: Compute average of readings from UUT with specified pressure applied.
*******************************************************************************)
procedure TfrmMain.adCaptureVals;
var
  i: Integer;
 begin
   case UUT.CalSequence of
     csCaptureZero:
       begin
         UUT.GetAvgVals(UUT.ZeroVals);
         UUT.ProcessADData(sgADData,1);
         UUT.PresMeasZeroPres := UUT.PresCurrent;   //record the actual pressure appled to the Sensotec.
         Memo1.Lines.Add('********  Zero Pressure A/D Measurements  **********');
         Memo1.Lines.Add('Average Pressure: '+ IntToStr(UUT.ZeroVals.PresADAvg));
         Memo1.Lines.Add('Max Pressure: ' + IntToStr(UUT.ZeroVals.PresADMax));
         Memo1.Lines.Add('Min Pressure: ' + IntToStr(UUT.ZeroVals.PresADMin));
         CheckADValues(csCaptureZero);    //verify values in range.
       end;
     csCaptureMax:
       begin
         UUT.GetAvgVals(UUT.MaxVals);
         UUT.ProcessADData(sgADData,2);
         UUT.PresMeasFullPres  := UUT.PresCurrent; //record the actual pressure appled to the Sensotec.
         Memo1.Lines.Add('***********  Max Pressure A/D Measurements  *************');
         Memo1.Lines.Add('Average Pressure: '+ IntToStr(UUT.MaxVals.PresADAvg));
         Memo1.Lines.Add('Max Pressure: ' + IntToStr(UUT.MaxVals.PresADMax));
         Memo1.Lines.Add('Min Pressure: ' + IntToStr(UUT.MaxVals.PresADMin));
       end;
     csCaptureRTZ:
       begin
         UUT.GetAvgVals(UUT.RTZVals);
         UUT.ProcessADData(sgADData,3);
         UUT.PresMeasReturnZero := UUT.PresCurrent; //record the actual pressure appled to the Sensotec.
         Memo1.Lines.Add('***********  Return to Zero A/D Measurements  *************');
         Memo1.Lines.Add('RTZ Average Pressure: '+ IntToStr(UUT.RtZVals.PresADAvg));
         Memo1.Lines.Add('RTZ Max Pressure: ' + IntToStr(UUT.RtZVals.PresADMax));
         Memo1.Lines.Add('RTZ Min Pressure: ' + IntToStr(UUT.RtZVals.PresADMin));
       end;
     csCaptureOnce:
       begin
          UUT.GetAvgVals(UUT.ZeroVals);
          UUT.ProcessADData(sgADData,1);
          UUT.WaitForAD := False;
       end;
   end;
   Memo1.Lines.Add(#13+'Pressure applied to UUT: ' +UUT.PresCurrent.ToString(ffFixed,5,2));
   if UUT.CalSequence <> csCaptureOnce then
       Memo1.Perform(EM_SCROLL,SB_LINEDOWN,0);
 end;



(*******************************************************************************
 CalLeakTest: Perform leak test at 440
*******************************************************************************)
procedure TfrmMain.CalLeakTest;
var
  i: Integer;
begin
 cbFastest.Checked := True;
 cbVent.Checked := True;
 Sleep(50);
 cbHighPress.Checked := True;
 UUT.SendCmd2(csLeakTest, AutoCal);
end;

(*******************************************************************************
 CalAutoCal: Run all steps necessary to autocalibrate the syringe
*******************************************************************************)
procedure TfrmMain.CalAutoCal;
begin
 if rgMode.ItemIndex <> 1 then    //ensure in programming mode
  begin
    rgMode.ItemIndex := 1;
    Sleep(200);         //allow relays time to change over
  end;
  UUT.SendCmd2(csBattV,bvStartCal); //records and displays battery voltage after programming
  LJ.FlushUART;         //make sure no remaining data in UART.
  LJ.PutBit(PTIP,1);          //turn on TEST IN PROGRESS lamp
  LJ.PutBit(PPASS,0);         //turn off pass lamp
  LJ.PutBit(PFAIL,0);         //turn off fail lamp
  sbStatus.Panels[0].Text := 'Executing auto-calibration routine.';
  //Memo1.Clear;
  Memo1.Lines.Add('Executing auto-calibration routine.');
  AutoCal := True;
  pnlCal.Enabled := False;  //Disable all buttons until routine is complete.
  UUT.ADCapture := adZero;  //must initialize.
  CalLeakTest;
  //calEnterCal;
end;

(*******************************************************************************
 calEnterCal: Configures RX state machine to look for CAL echo from UUT.
*******************************************************************************)
procedure TfrmMain.calEnterCal;
begin
  UUT.SendCmd2(csEnterCal, AutoCal);
end;

(******************************************************************************
 CalSetSN: Set the serial number in the UUT
*******************************************************************************)
procedure TfrmMain.CalSetSN;
begin
  UUT.SendCmd2(csSendSN);
end;

(*******************************************************************************
 CalReadSN: Reads the serial number back from the UUT
*******************************************************************************)
procedure TfrmMain.CalReadSN;
begin
  UUT.SendCmd2(csReadSN);
end;

(*******************************************************************************
CalCaptureZero: Captures the A/D data measured with zero pressure applied,
                     to be used in A1Calc and C1Calc.
*******************************************************************************)
procedure TfrmMain.CalCaptureZero;
begin
  UUT.NumReadsRequired := 15;
  UUT.SendCmd2(csCaptureZero, UUT.NumReadsRequired);
end;

(*******************************************************************************
CalCaptureMax: Captures the A/D data measured with Max pressure applied,
                     to be used in A1Calc and C1Calc
*******************************************************************************)
procedure TfrmMain.CalCaptureMax;
begin
  UUT.NumReadsRequired := 15;
  UUT.SendCmd2(csCaptureMax, UUT.NumReadsRequired);
end;

(*******************************************************************************
CalCaptureRtz: Captures the A/D data measured after returning to zero from max,
                     to be used in A1Calc and C1Calc.
*******************************************************************************)
procedure TfrmMain.CalCaptureRtz;
begin
  UUT.NumReadsRequired := 15;
  UUT.SendCmd2(csCaptureRtz, UUT.NumReadsRequired);
end;
(*******************************************************************************
 CalSendParams: Send Parameters to the UUT
*******************************************************************************)
procedure TfrmMain.CalSendParams;
begin
  UUT.SendCmd2(csSendParams);
end;

(*******************************************************************************
 CalExitCal: Causes UUT to exit CAL routine.
*******************************************************************************)
procedure TfrmMain.CalExitCal;
begin
  sbStatus.Panels[0].Text := 'Exiting Calibration Routine.';
  UUT.SendCmd2(csExitCal);
end;


procedure  TfrmMain.ScrollToBottom;
  begin
    Memo1.Lines.Add('');     //add a blank line to move the cursor to the last line.
    SendMessage(Memo1.Handle, EM_SCROLLCARET, 0, 0);
  end;

(*******************************************************************************
 ScrollRichEditToBottom: This function auto scrolls the referenced RichEdit
                         when text is added that would cause it to fill the
                         RichEdit to the point that it needs to scroll.
******************************************************************************)
function TfrmMain.ScrollRichEditToBottom(var RichEdit: TRichEdit): Boolean;
var
  l: Integer;
begin
  Result := true;
  l := Length(RichEdit.Text);
  try
    RichEdit.SelStart := l;
    RichEdit.Perform(EM_SCROLLCARET, 0, 0);
  except
    try
      RichEdit.SelStart := l;
      SendMessage(RichEdit.Handle, EM_SCROLLCARET, 0, 0);
    except
      Result := False;
    end;
  end;
end;

{ TTester.constructor: Upon creation of a TTester, TLabJack, TUUT, and TTestHead all need to be instantiated. }
constructor TTester.Create;
begin
  try
    LJ := TLabJack.Create;
    TH := TTestHead.Create;
    UUT := TUUT.Create;
  except
    ShowMessage('Error creating TTester members.');
  end;
end;

(******************************************************************************
  LoadIniSettings: Loads the configuration from the last setting and ensures
                   that a hardware change has not occured. If so, allows the
                   user to configure the system to match the change.
*******************************************************************************)
procedure TfrmMain.LoadIniSettings;
var
  i, cnt, tmpSn: Integer; CfgErr: Boolean;
begin
  //Display the current found LJs on the memo
  for i := Low(SysLabJacks) to High(SysLabJacks) do
    Memo1.Lines.Add('***LJ '+ i.ToString + '***' +
                    '   S/N: '+SysLabJacks[i].Sn.ToString +
                    '   ID: '+ SysLabJacks[i].Id.ToString +
                    '   ADDR: '+ FloatToStr(SysLabJacks[i].Addr));
  //NOw compare the last session values to the current found LJs
  AppSettings := TIniFile.Create(ChangeFileExt(Application.ExeName, '.INI'));
  CfgErr := False;   //initialize
  try
    cnt := AppSettings.ReadInteger('LJ_COUNT', 'COUNT', 0);
      if cnt <> High(SysLabJacks) then
        CfgErr := True
      else
        begin
          for i := 0 to High(SysLabJacks) do
            begin
              tmpSN := AppSettings.ReadInteger('LJCONFIG', 'LJ_'+i.ToString+'_SN',0);
              if tmpSN <> SysLabJacks[i].Sn then
                begin
                  CfgErr := True;
                  break;
                end;
            end;
        end;
      if CfgErr then
        Memo1.Lines.Add('LJ configuration does not match previous.')
        else
         Memo1.Lines.Add('LJ configuration matches previous.');
  except
    ShowMessage('Error while processing application setting .ini file.');
  end;
end;

(******************************************************************************
  ConfigCalAccess: Configures the calibration panels & Buttons to only allow
                   access to buttons based on the state of the calibration
                   routine.
*******************************************************************************)
procedure TfrmMain.ConfigCalAccess;
begin
  if (UUT.CalActive) and (UUT.UUTReady) and (not AutoCal) then
    begin
      pnlCal.Enabled := True;
      pnlTest.Enabled := True;
      pnlProgCal.Enabled := False;  //already in cal mode so can't use these
      btnProgCal.Enabled := False;   //already in cal mode so can't restart;
    end;
  if (UUT.CalActive) and (UUT.UUTReady) and (AutoCal) then
    begin
      pnlCal.Enabled := False;       //disable in autocal
      pnlTest.Enabled := False;      //disable in autocal
      pnlProgCal.Enabled := False;  //already in cal mode so can't use these
      btnProgCal.Enabled := False;   //already in cal mode so can't restart;
    end;
  if (not UUT.CalActive) or (not UUT.UUTReady) then
    begin
      pnlCal.Enabled := False;
      pnlTest.Enabled := False;
      pnlProgCal.Enabled := True;  //already in cal mode so can't use these
      btnProgCal.Enabled := True;   //already in cal mode so can't restart;
    end;
end;

(******************************************************************************
  dpProgOK: When the programmer completes programming, if everything passed,
            this packet will be received and event will be triggered, setting
            the UUT.UUTProgrammed flag and allowing further processing.
*******************************************************************************)
procedure TfrmMain.dpProgOKPacket(Sender: TObject; const Str: string);
begin
  tmrProg.Enabled := False;
  UUT.UUTProgrammed := True;
  MemMsg('UUT was programmed successfully.');
  if CalAfterPgm then
    CalAutoCal     //Enter autocalibration routine
    else           //allow user to calibrate manually
      begin
        rgMode.ItemIndex := 1;      //configure relays for calibration mode
        pnlProgCal.Enabled := True;  //already in cal mode so can't use these
        btnProgCal.Enabled := True;   //already in cal mode so can't restart;
      end;
end;

procedure TfrmMain.edSNChange(Sender: TObject);
begin
  if edWorkOrder.Text <> '' then
    ConfigCalAccess;
end;

procedure TfrmMain.edSNEnter(Sender: TObject);
begin
  if edSN.Text = '' then
    ResetGui;
end;

end.
